package com.yumeng.framework.core.mvc;

import com.yumeng.common.api.ApiCode;
import com.yumeng.common.api.ApiResult;
import com.yumeng.common.exception.*;
import com.yumeng.common.auth.BaseAuthHelper;
import com.yumeng.framework.auth.exception.*;
import jakarta.annotation.Nullable;
import jakarta.servlet.ServletException;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.util.List;

/**
 * 统一捕捉异常，返回给前台一个json信息，前台根据这个信息显示对应的提示，或者做页面的跳转。
 *
 * @author wxd
 * @date 2024/6/5 下午9:15
 */
@Slf4j
public class BaseGlobalExceptionHandler extends ResponseEntityExceptionHandler {

    /** 属性名称-异常信息 */
    private static final String errorInfoAttributeName = "yumeng_BaseGlobalExceptionHandler_error_info";

    /**
     * 统一异常前缀
     * 子类中可以覆盖
     */
    private final String globalExceptionFlag = "EXC:";

    public String getGlobalExceptionFlag() {
        return globalExceptionFlag;
    }

    private int getWebRequestScope(){
        return WebRequest.SCOPE_REQUEST;
    }


    //region 全局异常自定义扩展

    @ExceptionHandler({
            //common
            BusinessException.class,
            HttpClientException.class,
            YumengException.class,
            //auth
            NormalAuthException.class,
            TokenAuthException.class,
            SecurityAuthException.class,
            ShiroAuthException.class, // html访问会被封装成了ServletException，json接口调用异常没有被封装

            NormalPermException.class,
            //java
            ServletException.class,
            RuntimeException.class,
            Exception.class
    })
    public final ResponseEntity<Object> customHandleException(Exception ex, WebRequest request) throws Exception{
        //部分认证框架需要自己处理认证异常
        if (BaseAuthHelper.needThrowException(ex)){
            throw ex;
        }
        HttpStatus status = HttpStatus.OK;
        ApiCode apiCode = null;

        if (ex instanceof BaseException bsEx){
            if (bsEx.hasCustomApiCode()){
                apiCode = bsEx.getApiCode();
            }
        }

        if(apiCode == null){
            if (ex instanceof ServletException){
                apiCode = ApiCode.SERVLET_ERROR;
            }else if (ex instanceof DemoException){
                apiCode = ApiCode.BUSINESS_EXCEPTION;
            }else if (ex instanceof BusinessException){
                apiCode = ApiCode.BUSINESS_EXCEPTION;
            }else if (ex instanceof HttpClientException){
                apiCode = ApiCode.YUMENG_EXCEPTION;
            }else if (ex instanceof YumengException){
                apiCode = ApiCode.YUMENG_EXCEPTION;
            }else if (ex instanceof RuntimeException){
                apiCode = ApiCode.RUNTIME_ERROR;
            }
        }

        if (apiCode == null){
            apiCode = ApiCode.UNMATCH_ERROR;
        }
        HttpHeaders headers = new HttpHeaders();
        String msg = ex.getCause() != null ? ex.getCause().getLocalizedMessage() : ex.getLocalizedMessage();
        return doHandleException(ex, null, headers, status, request, apiCode, msg);
    }

    //endregion

    //region Internal异常（ResponseEntityExceptionHandler已定义的）

    @Nullable
    @Override
    protected final ResponseEntity<Object> handleExceptionInternal(Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) {
        ApiCode defaultApiCode = getApiCode(ex);
        String msg = getExceptionMessage(ex);
        return doHandleException(ex, body, headers, statusCode, request, defaultApiCode, msg);
    }


    //endregion

    //region 通用处理

    @Override
    protected final ResponseEntity<Object> createResponseEntity(Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) {
        Object currBody = body;
        ErrorInfo info = (ErrorInfo) request.getAttribute(errorInfoAttributeName, getWebRequestScope());
        String msg = null;
        ApiCode defaultApiCode = null;
        if (info != null) {
            msg = info.getErrMsg();
            defaultApiCode = info.getApiCode();
        }else {
            msg = "ErrorInfo is null";
        }
        ApiResult newBody = ApiResult.resultDataMsg(defaultApiCode, currBody, msg);
        return new ResponseEntity<>(newBody, headers, statusCode);
    }

    /**
     * 公共处理返回信息
     *
     * @param ex
     * @param body
     * @param headers
     * @param statusCode
     * @param request
     * @param defaultApiCode
     * @param errMsg
     * @return
     */
    protected ResponseEntity<Object> doHandleException(Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request, ApiCode defaultApiCode, String errMsg){
        String msg = StringUtils.isNotBlank(errMsg) ? errMsg : getExceptionMessage(ex);
        log.error("{}{}", getGlobalExceptionFlag(), msg);
        log.error(ex.toString(), ex);
        ErrorInfo info = new ErrorInfo(msg, defaultApiCode);
        request.setAttribute(errorInfoAttributeName, info, getWebRequestScope());
        return super.handleExceptionInternal(ex, body, headers, statusCode, request);
    }
    protected final ResponseEntity<Object> doHandleException(Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request, ApiCode defaultApiCode){
        return doHandleException(ex, body, headers, statusCode, request, defaultApiCode, null);
    }

    /**
     * 根据异常类型，获取异常msg
     *
     * @param ex
     * @return
     */
    private String getExceptionMessage(Exception ex) {
        String errMsg = "";
        if (ex instanceof BindException subEx) {// || ex instanceof MethodArgumentNotValidException
            List<ObjectError> errorList = subEx.getBindingResult().getAllErrors();
            for(ObjectError error : errorList){
                if (error instanceof FieldError subItem){
                    errMsg += String.format("[%s]-%s;", subItem.getField(), subItem.getDefaultMessage());
                }else{
                    errMsg += String.format("%s;", error.getDefaultMessage());
                }
            }
            errMsg = String.format("以下参数验证异常：%s", errMsg);
        }else if (ex instanceof MissingServletRequestParameterException subEx) {
            errMsg = String.format("参数缺失：%s[%s]", subEx.getParameterName(), subEx.getParameterType());
        }else{
            //TODO 是否需要判断ex.getCause()
            errMsg = ex.getCause() != null ? ex.getCause().getLocalizedMessage() : ex.getLocalizedMessage();
        }
        return errMsg;
    }

    /**
     * 根据异常类型，获取ApiCode
     * @param ex
     * @return
     */
    private ApiCode getApiCode(Exception ex){
        ApiCode apiCode = null;
        if (ex instanceof MissingServletRequestParameterException || ex instanceof BindException){
            apiCode = ApiCode.BAD_REQUEST;
        }else {
            apiCode = ApiCode.ERROR;
        }
        return apiCode;
    }

    //endregion


    @Getter
    @Setter
    @AllArgsConstructor
    static class ErrorInfo{

        private String errMsg;
        private ApiCode apiCode;

    }


}
